/*
 MIDAS Context Based Routing
 cbr.cc
 Copyright (c) 2007 Radoslaw Olgierd Schoeneich
 Copyright (c) 2007 Bartlomiej Zolnierkiewicz
 MEAG IT PW
 */

#include <fstream>
#include <iostream>
#include <sstream>
#include <assert.h>
#include <pthread.h>

#include "scheduler.h"
#include "packet.h"

#include "fifo.h"

#include "cbr.h"
#include "cbr_packet.h"
#include "cbr_daemon.h"
#include "cbr_crt.h"
#include "cbr_debug.h"

using namespace std;

// by default off, use -p/-1/-2/-3/-4/-5 parameters to turn it on
unsigned long log_packets = 0;

// by default off, use -x parameter to turn it on
bool local_context_model = false;
// force on during testing phase
//bool local_context_model = true;

/**
 * Dumps CBR header.
 *
 * @param gh generic CBR header
 * @param start pointer to the data buffer
 */

static void dump_cbr_header(hdr_cbr *gh, u_int8_t *start) {
	// 8 bytes of generic header stored in hdr_cbr
	cbr_log(" CBR generic header (12 bytes):\n");
	cbr_log("\ttype: 0x%02x (%u)\n", gh->type, gh->type);
	cbr_log("\tsize: 0x%04x (%u)\n", gh->size, gh->size);
	cbr_log("\tsrc: 0x%08x (%u)\n", gh->src, gh->src);
	cbr_log("\tretr_count: 0x%02x (%u)\n", gh->retr_count, gh->retr_count);

	// bytes 9-12 (previous hop ID) of generic header
	// (always get it from data to be sent/received)
	cbr_log("\tprev_hop: 0x%08x (%u)\n",
			*(u_int32_t *)(start + 8), *(u_int32_t *)(start + 8));
}

/**
 * Prepares CBR packet to be sent through the network interface.
 *
 * @param data CBR packet to be sent (byte representation)
 * @param pkt CBR packet to send (object representation)
 */

void CBR::cbr_prep_send(char *data, Packet *pkt) {
	PacketData *pktdata = (PacketData *)pkt->userdata();

	// encapsulate generic CBR header
	hdr_cbr *gh = hdr_cbr::access(pkt);
	u_int8_t *start = (u_int8_t *)(CBR_ID_SIZE + data);
	// cannot use serializer bacause hdr_cbr is packed
	//	serializer ser(start, sizeof(hdr_cbr));

	*start = gh->type;
	*(u_int16_t *)(start + 1) = gh->size;
	*(u_int32_t *)(start + 3) = gh->src;
	*(start + 7) = gh->retr_count;

	// get the destination node ID
	hdr_cmn *ch = hdr_cmn::access(pkt);

	if (is_central_node && !is_firewalled)
		// for RREQ1/2 and RREP tell the destination node to
		// communicate directly with the previous node
		*(u_int32_t *)(start + 8) = ch->prev_hop_;
	else
		*(u_int32_t *)(start + 8) = localID;

	// copy the data
	memcpy((char *)(CBR_ID_SIZE + data) + sizeof(hdr_cbr), pktdata->data(),
			gh->size - sizeof(hdr_cbr));

	dump_cbr_header(gh, start);

	if (cbr_log_cbr_packet(gh->type)) {
cbr_log(" CBR packet:\n");
				dump_cbr_data(CBR_ID_SIZE + data, gh->size);
	}

	*(cbr_ID *)data = ch->next_hop_;
}

/**
 * Sends CBR packet through the network interface.
 *
 * @param netif network interface
 * @param data CBR packet to be sent (byte representation)
 */

void CBR::cbr_send(cbr_NetworkInterface *netif, char *data) {
	// if in managed mode use IP encapsulation otherwise use Ethernet
	cbr_sendto(this, netif->getSocket(), netif->getIndex(), 
	CBR_ID_SIZE + data, *(cbr_ID *)data, netif->isManaged());
}

void cbr_NetInterfaces::send(CBR *agent, char *data) {
	interfaces_list::const_iterator i;

	for (i = interfaces.begin(); i != interfaces.end(); i++) {
		unsigned char msg_type = *(data + CBR_ID_SIZE);

		// if this is managed interface forward all RREQ1/2
		// and HELLO packets to the Central Node
		if (!agent->IsCentralNode() && (*i)->isManaged()) {
			switch (msg_type & CBRTYPE_MASK) {
			case CBRTYPE_HELLO:
			case CBRTYPE_RREQ1:
			case CBRTYPE_RREQ2:
				*(cbr_ID *)data = agent->cn.getID();
				break;
			}
		}

cbr_log("SEND: packet type=%02x next_hop=%d\n",
			msg_type, *(cbr_ID *)data);

				agent->cbr_send(*i, data);
	}
}

/**
 * Processes new CBR packet received from the network interface.
 *
 * @param netif network interface
 * @param data CBR packet to be processed (byte representation)
 *
 * @return processed CBR packet (object representation)
 */

Packet *CBR::cbr_post_recv(char *data) {
	// contruct new Packet instance
	Packet *pkt = Packet::alloc();
	int len = cbr_Packet::maxSize - sizeof(hdr_cbr);

	// extract generic CBR header
	hdr_cbr *gh = hdr_cbr::access(pkt);
	u_int8_t *start = (u_int8_t *)data;
	// cannot use serializer bacause hdr_cbr is packed
	//	serializer ser(start, sizeof(hdr_cbr));

	gh->type = *start;
	gh->size = *(u_int16_t *)(start + 1);
	gh->src = *(u_int32_t *)(start + 3);
	gh->retr_count = *(start + 7);

	// TODO: use only gh->prev_hop and remove hdr_cmn
	hdr_cmn *ch = hdr_cmn::access(pkt);
	ch->prev_hop_ = *(u_int32_t *)(start + 8);

	// copy the data
	PacketData *pktdata = new PacketData(len);
	memcpy(pktdata->data(), (char *)data + sizeof(hdr_cbr), len);

	pkt->setdata(pktdata);

cbr_log("RECV: packet type=%02x next_hop=%d\n",
		gh->type, ch->next_hop_);
		dump_cbr_header(gh, start);

	if (cbr_log_cbr_packet(gh->type)) {
cbr_log(" CBR data:\n");
				dump_cbr_data(data, gh->size);
	}

	return pkt;
}

/**
 * Receives new CBR packet.
 *
 * @param buf data buffer
 */

void CBR::cbr_recv(char *buf) {
	char *data = new char[cbr_Packet::maxSize];

	memcpy(data, buf, cbr_Packet::maxSize);

	pthread_mutex_lock(&cbr_in_pkts_mutex);
	fifo_enqueue(cbr_in_pkts_fifo, data);
	pthread_cond_signal(&cbr_in_pkts_cv);
	pthread_mutex_unlock(&cbr_in_pkts_mutex);
}

/**
 * Checks all interfaces for new incoming data.
 *
 * @param agent CBR agent
 *
 * Checks all interfaces for new incoming data.  If there are some
 * new CBR packets then it receives and proccesses them.
 */

void cbr_NetInterfaces::recv(CBR *agent) {
	interfaces_list::const_iterator i;
	char *buf = new char[cbr_Packet::maxSize];

	// check all interfaces for new packets
	for (i = interfaces.begin(); i != interfaces.end(); i++) {
		while (cbr_select(agent, (*i)->getSocket(), buf, (*i)->isManaged()) > 0)
			agent->cbr_recv(buf);
	}

	delete[] buf;
}

/**
 * Adds interfaces to mapping caches.
 *
 * @param agent CBR agent
 *
 * Adds all interfaces belonging to this CBR @agent to mapping
 * caches (IP cache for managed/wired interfaces and MAC cache
 * for ad-hoc/wireless interfaces).
 */

void cbr_NetInterfaces::add_to_mapping_caches(CBR *agent) {
	cbr_ID id = agent->getLocalID();
	interfaces_list::const_iterator i;

	cbr_log	("initializing IP and MAC mappings:\n");

	// check all interfaces
	for (i = interfaces.begin(); i != interfaces.end(); i++) {
		//		if ((*i)->isManaged()) {
		ip_addr ip;

		ip = (*i)->getIpAddress();
		//if (get_if_ip_addr((*i)->getName(), &ip) == 0)
		continue;

		agent->ipcache.insert(id, ip);
		//		} else {
		mac_addr mac;

		// if can't get MAC address just ignore this interface
		if (get_if_hw_addr((*i)->getName(), &mac) == 0)
		continue;

		agent->maccache.insert(id, mac);
		//		}
	}
}

// for inet_ntoa()
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

/**
 * Prints Central Node banner.
 */

void cbr_CentralNode::banner() {
	struct in_addr in;
	in.s_addr = ip;

	cbr_log("Central Node - ID: %u IP: %s\n", id, inet_ntoa(in));
}

/**
 * Parses Central Node configuration.
 *
 * @param input input stream
 *
 * Parses Central Node configuration from @input input stream
 * ("cn.txt" file).
 *
 * @return "true" on success, "false" otherwise.
 */

bool cbr_CentralNode::parse(std::istream& input) {
	std::istringstream converter;
	std::string buff;
	std::string ip_str, mac_tag, mac_str;

	do {
		std::getline(input, buff);
	} while (buff.empty() || buff.at(0) == '#');

	converter.str(buff);
	converter >> id;

	do {
		std::getline(input, buff);
	} while (buff.empty() || buff.at(0) == '#');

	converter.clear();
	converter.str(buff);
	converter >> ip_str;
	converter >> mac_tag;

	if (ip_str.empty())
		return false;

	// force interface MAC using configuration file
	// - used for testing purposes
	if (mac_tag.empty() == 0) {
		if (mac_tag.compare("mac") == 0) {
			converter >> mac_str;
			if (mac_str.empty()) {
				cbr_log("error: no MAC given for CN\n");
				return false;
			}
		} else {
			cbr_log("error: invalid mode given\n");
			return false;
		}
	}

	ip = inet_addr((const char *)ip_str.c_str());
	ip_str.empty();

	if (mac_str.empty() == 0) {
		convertstrtomac(mac_str.c_str(), &mac);
		mac_str.empty();
	}

	return true;
}

/**
 * Parses Network Interfaces configuration.
 *
 * @param input input stream
 *
 * Parses Network Interfaces configuration from @input input stream
 * ("netifs.txt" file).
 *
 * @return "true" on success, "false" otherwise.
 */

bool cbr_NetInterfaces::parse(std::istream& input) {
	std::istringstream converter;
	std::string buff;
	int num_if;

	do {
		std::getline(input, buff);
	} while (buff.empty() || buff.at(0) == '#');

	converter.str(buff);
	converter >> num_if;

	if (num_if <= 0) {
cbr_log		("error: no network interfaces given\n");
		return false;
	}

	for (int i = 0; i < num_if; i++) {
		std::string if_str, if_ip_address, mode_str;
		bool force_mode = false, managed = false;

		do {
			std::getline(input, buff);
		}while (buff.empty() || buff.at(0) == '#');

		converter.clear();
		converter.str(buff);
		converter >> if_str;
		converter >> if_ip_address;
		converter >> mode_str;

		if (if_str.empty()) {
			cbr_log("error: no interface name found\n");
			return false;
		}

		// force interface mode (Ad-Hoc or Managed) using
		// configuration file - used for testing purposes
		if (mode_str.empty() == 0) {
			if (mode_str.compare("adhoc") == 0) {
				managed = false;
			} else if (mode_str.compare("managed") == 0) {
				managed = true;
			} else {
				cbr_log("error: invalid mode given\n");
				return false;
			}

			force_mode = true;
		}

		cbr_log("interface: %s %s\n", if_str.c_str(), mode_str.c_str());

		// add interface to the list of interfaces
		add(if_str.c_str(), if_ip_address.c_str(), force_mode, managed);

		if_str.empty();
		if_ip_address.empty();
	}

	return true;
}

/**
 * Parses NIS configuration.
 *
 * @param input input stream
 *
 * Parses NIS configuration from @input input stream
 * ("nis.txt" file).
 *
 * @return "true" on success, "false" otherwise.
 */

bool cbr_NIS::parse(std::istream& input, cbr_IPCache *cache) {
	std::istringstream converter;
	std::string buff;
	int num_ip;

	do {
		std::getline(input, buff);
	} while (buff.empty() || buff.at(0) == '#');

	converter.str(buff);
	converter >> num_ip;

	if (num_ip <= 0)
		return false;

cbr_log	("NIS mappings:\n");

	for (int i = 0; i < num_ip; i++) {
		cbr_ID id;
		std::string ip_str;
		ip_addr ip;

		do {
			std::getline(input, buff);
		}while (buff.empty() || buff.at(0) == '#');

		converter.clear();
		converter.str(buff);
		converter >> id;
		converter >> ip_str;

		if (ip_str.empty())
		return false;

		cbr_log(" %u -> %s\n", id, ip_str.c_str());

		ip = inet_addr((const char *)ip_str.c_str());
		ip_str.empty();

		cache->insert(id, ip);
	}

	return true;
}

/**
 * Sets Context Based Address for the local CBR node.
 *
 * @param addr context based address of the local node
 *
 * Sets new Context Based Address for the local CBR node
 * and frees the old Context Based Address (if exists).
 */

void CBR::setLocalCBA(cbr_ContextAddress* addr) {
	cbr_ContextAddress* old_addr = localAddr;

	localAddr = addr;

	if (old_addr)
		delete old_addr;
}

/**
 * CBR class constructor.
 *
 * @param id ID of the local node
 * @param addr context based address of the local node
 * @param netifs_cfg_file network interfaces configuration file
 * @param nis_cfg_file NIS mappings configuration file
 * @param model_cfg_file Context Model configuration file
 * @param cn_cfg_file Central Node configuration file
 * @param firewalled "firewalled" mode flag
 * @param central_node Central Node flag (set if node is a Central Node)
 *
 * Constructs new CBR class instance given the ID and the context based
 * address of the local node.  Initializes mtimer and crtimer
 * instances to point ->agent to this instance of the CBR class.
 * It also initializes transaction ID counter to the starting value (2).
 */

CBR::CBR(cbr_ID id, cbr_ContextAddress* addr, const char *netifs_cfg_file,
		string nis_cfg_file, const char *model_cfg_file, string cn_cfg_file,
		bool firewalled, bool central_node) :
	mtimer(this), crtimer(this), last_trID(0) {
	//	cbr_debug("got CBR::CBR(%d, '%s')\n", id, addr->getAsString().c_str());

	// read domain model from the file
	if (local_context_model) {
		// FIXME: segfaults g++ 2.96?
		std::ifstream f(model_cfg_file);
		// TODO: return/check error value
		model.parse(f);
		f.close();
	}

	// initialize local node ID
	localID = id;

	setLocalCBA(addr);

	is_firewalled = firewalled;

	// FIXME: central node vs ad-hoc mode?
	is_central_node = central_node;

	// get Central Node data from the file
	if (cn_cfg_file.empty() == 0) {
		std::ifstream h(cn_cfg_file.c_str());
		if (cn.parse(h))
			cn.banner();
		h.close();
	}

	// both caches are needed for dynamic managed<->ad-hoc transitions
	ipcache.init(this);
	maccache.init(this);

	// get network interfaces from the file
	std::ifstream g(netifs_cfg_file);
	// TODO: return/check error value
	netifs.parse(g);
	g.close();

	netifs.add_to_mapping_caches(this);

	// get NIS mappings from the file (optional)
	if (nis_cfg_file.empty() == 0) {
		std::ifstream i(nis_cfg_file.c_str());
		// TODO: return/check error value
		nis.parse(i, &ipcache);
		i.close();
	}
}

/**
 * CBR class destructor.
 *
 * Destructs instance of CBR class.  Currently only deallocates memory used
 * for storing context based address of the local node.
 */

CBR::~CBR() {
	maccache.purge();
	ipcache.purge();

	if (localAddr != (cbr_ContextAddress*) 0)
		delete localAddr;
}

/**
 * Forwards packet.
 *
 * @param next_hop ID of the node to forward packet to
 * @param pkt generic packet
 * @param delay delay after which forward the packet (in seconds)
 *
 * Forwards packet @pkt to the node given by @next_hop
 * with the delay @delay.
 *
 * Called by CBR::sendPacket, CBR::recvRequest, CBR::recvReply, CBR::recvData.
 */

void CBR::forward(cbr_ID next_hop, Packet* pkt, double delay) {
	struct hdr_cmn *ch= HDR_CMN(pkt);

	hdr_cbr* generic_hdr = hdr_cbr::access(pkt);
	generic_hdr->retr_count = 0;

	// if the next node is a local node then don't send a packet
	// (assumes that localID != IP_BROADCAST)
	if (next_hop == localID)
		return;

	if ((unsigned)next_hop == IP_BROADCAST)
		cbr_debug("FORWARDING PACKET TO <broadcast>\n");
	else
		cbr_debug("FORWARDING PACKET TO %u\n", (unsigned)next_hop);

	//	ch->prev_hop_ = localID;
	// used by CBR::cbr_prep_send()
	ch->next_hop_ = next_hop;

	char *data = new char[CBR_ID_SIZE + cbr_Packet::maxSize];

	memset(data, 0, CBR_ID_SIZE + cbr_Packet::maxSize);
	cbr_prep_send(data, pkt);

	Packet::free(pkt);

	pthread_mutex_lock(&cbr_out_pkts_mutex);
	fifo_enqueue(cbr_out_pkts_fifo, data);
	pthread_cond_signal(&cbr_out_pkts_cv);
	pthread_mutex_unlock(&cbr_out_pkts_mutex);
}
;

/**
 * Sends CBR packet.
 *
 * @param p CBR packet
 * @param fwdr ID of the forwaring node
 * @param delay delay after which send the packet
 *
 * Called by CBR::sendReply, CBR::recvReply and CBR::sendHello.
 */

void CBR::sendPacket(cbr_Packet* p, cbr_ID fwdr, double delay) {
	Packet *pkt = p->pkt();

	forward(fwdr, pkt, delay);
}

u_int32_t CBR::getTransactionID() {
	u_int32_t trID = sclock();

	cbr_debug("%s: new trID = %u\n", __func__, trID);

	if (last_trID >= trID) {
		cbr_debug("%s: last_trID (%u) > trID (%u), fixing\n",
				__func__, last_trID, trID);
		trID = last_trID + 1;
	}

	last_trID = trID;

	return trID;
}

/**
 * Receives packet.
 *
 * @param data CBR packet (byte representation)
 *
 * Process the received packet depeding on its type.
 *
 * Called when new packet arrives from the other node.
 */

void CBR::recv(char *data, Handler*) {
	Packet *pkt = cbr_post_recv(data);
	struct hdr_cmn* ch= HDR_CMN(pkt);

	// do nothing if prev_hop_ is localID
	if (ch->prev_hop_ == localID) {
		cbr_debug("RECV: dropping packet - local node is a previous node\n");
		Packet::free(pkt);
		return;
	}

	hdr_cbr* generic_hdr = hdr_cbr::access(pkt);
	u_int8_t pkt_type = generic_hdr->type & CBRTYPE_MASK;
	switch (pkt_type) {
	case CBRTYPE_HELLO:
		if (generic_hdr->src != (unsigned) localID)
			recvHello(pkt);
		else
			// FIXME: can this actually happen?
			Packet::free(pkt);
		break;
	case CBRTYPE_RREQ1:
	case CBRTYPE_RREQ2:
		recvRequest(pkt, NULL, 0.0);
		break;
	case CBRTYPE_RREP:
		recvReply(pkt);
		break;
	case CBRTYPE_DATA:
		recvData(pkt);
		break;
	default:
cbr_log("RECV : invalid packet type!\n");
					Packet::free(pkt);
		break;
	}
}

/**
 * Forwards RREQ packet.
 *
 * @param addr Context Based Address
 * @param rreq_packet cbr_RReqPacket instance
 * @param delay delay after which send the packet
 *
 * Forward RREQ packet:
 *
 * - If in infrastructure mode - forward it Central Node.
 *
 * - If in ad-hoc mode - forward it to known forwards and if none are found
 *   just broadcast it.
 */

void CBR::forward_rreq(const cbr_ContextAddress *addr,
		cbr_RReqPacket *rreq_packet, double delay) {
	Packet *pkt = rreq_packet->pkt();

	// find forwarders in CBR routing table (crtable) given the
	// context based address of the packet and then forward it
	std::set<cbr_ID>* forwarders;
	forwarders = model.getForwarders(*addr, crtable,
			rreq_packet->isMulticast(), localID);

	// if no forwards were found just broadcast the packet
	if (1) { //forwarders->empty()) { /* FIXME */
		forward(IP_BROADCAST, pkt, delay);
	} else {
		std::set<cbr_ID>::iterator i;
		for (i = forwarders->begin(); i != forwarders->end(); i++) {
			Packet *newPkt = pkt->copy();
			forward(*i, newPkt, delay);
		}

		delete forwarders;
		Packet::free(pkt);
	}
}

/**
 * Get all sources for a given concept.
 *
 * @param crtable CBR routing table
 * @param c CBR concept
 *
 * Called by CBR::forward_rreq_with_restrictions().
 *
 * @returns set of source node IDs
 */

std::set<cbr_ID>* CBR::getSourcesAll(const cbr_CRTable &crtable, cbr_Concept c) {
	std::set<cbr_ID>* sources;
	cbr_ConceptsSet tmp, sub, sup;

	cbr_getSubConcepts(c, tmp);
	cbr_ContextModel::joinSets(sub, tmp);

	if (!sub.empty()) {
		sources = model.getSources(crtable, sub, localID);

		if (sources->empty()) {
			cbr_ConceptsSet rt, tmp_rt, sup_rt;
			cbr_ConceptsSet::const_iterator i;

			rt = crtable.getConcepts();

			for (i = rt.begin(); i != rt.end(); i++) {
				cbr_getSuperClasses(*i, tmp_rt);
				cbr_ContextModel::joinSets(sup_rt, tmp_rt);
			}

			sources = model.getSources(crtable, sup_rt, localID);
		}

		if (!sources->empty())
			return sources;
	}

	int level = 0;

	do {
		cbr_getSuperConcepts(c, level, tmp);

		if (!crtable.match(tmp))
			level++;
		else
			cbr_ContextModel::joinSets(sup, tmp);
	} while (!sup.empty());

	return model.getSources(crtable, sub, localID);
}

/**
 * Find CBR id in a set of ids.
 *
 * @param s CBR ids set
 * @param id CBR id
 *
 * Looks for CBR id @a id in a @a s set of CBR ids.
 *
 * @returns '1' on success, '0' otherwise.
 *
 * TODO: why ->find doesn't work on set?
 */

static bool find_id_in_set(std::set<cbr_ID>* s, cbr_ID id) {

	std::set<cbr_ID>::iterator i;

	for (i = s->begin(); i != s->end(); i++) {
		if (*i == id)
			return 1;
	}

	return 0;
}

/**
 * Forwards RREQ packet with_restrictions.
 *
 * @param addr Context Based Address
 * @param rreq_packet cbr_RReqPacket instance
 * @param delay delay after which send the packet
 */

void CBR::forward_rreq_with_restrictions(const cbr_ContextAddress *addr,
		cbr_RReqPacket *rreq_packet, double delay) {
	Packet *pkt = rreq_packet->pkt();
	cbr_ConceptsSet econcepts;
	cbr_ConceptsSet::iterator e;
	int i = 0; // sizeof of econcepts

	cbr_getElementaryConcepts(addr->getAsString().c_str(), econcepts);

	for (e = econcepts.begin(); e != econcepts.end(); e++)
		i++;

	std::set<cbr_ID> *fl= NULL, *sources[i];

	while (fl->empty()) {
		int j = 0, k = 0;

		for (e = econcepts.begin(); e != econcepts.end(); e++) {
			for (k = 0; k < i; k++)
				sources[k] = getSourcesAll(crtable, *e);
		}

		while (j <= i) {
			k = 0;
			while (k < i) {
				std::set<cbr_ID>* set_k = sources[k];
				std::set<cbr_ID>::iterator id;
				int l = 0;

				for (id = set_k->begin(); id != set_k->end(); id++) {
					int losses = 0;
					std::set<cbr_ID>* set_l = sources[l];

					while (l < i) {
						if (l != k && !find_id_in_set(set_l, *id))
							losses++;
					}
					l++;
					if (losses <= j)
						fl = model.getForwarders(*id, crtable, localID);
				}
				k++;
			}
			if (!fl->empty())
				break;
			j++;
		}
		if (j == i)
			break;
	}

	if (!fl->empty()) {
		std::set<cbr_ID>::iterator id;

		for (id = fl->begin(); id != fl->end(); i++) {
			Packet *newPkt = pkt->copy();
			forward(*id, newPkt, delay);
		}
	}
}

/**
 * Handles received packets of type RREQ1 and RREQ2.
 *
 * @param pkt generic packet
 * @param rreq_packet cbr_RReqPacket instance
 * @param delay delay (in seconds)
 *
 * Used for all incoming and outgoing RREQ1/RREQ2 packets.
 *
 * Called by CBR::recv and CBR::sendDataMessage.
 */

void CBR::recvRequest(Packet* pkt, cbr_RReqPacket* rreq_packet, double delay) {
	// if given generic packet instance construct cbr_RReqPacket instance
	// otherwise just get generic packet instance from cbr_RReqPacket
	if (pkt != NULL)
		rreq_packet = new cbr_RReqPacket(pkt);
	else
		pkt = rreq_packet->pkt();

	struct hdr_cmn *ch= HDR_CMN(pkt);
	cbr_ID prev_hop = ch->prev_hop_, src = rreq_packet->getSource();

	u_int32_t trID = rreq_packet->getTransactionID();

	// discard the packet if the packet with the same transaction ID
	// and the same source node has already been received
	if (reqtable.getPrevHop(trID, src, NULL)) {
cbr_log("RECV: dropping RREQ packet - packet w/ trID=%u "
		        "src=%u has already been received\n", trID, src);
				Packet::free(pkt);
		delete rreq_packet;
		return;
	}

	hdr_cbr* generic_hdr = hdr_cbr::access(pkt);
	cbr_debug("%sRECV : RREQ%c from %u @ %u | %d | %d\n",
			(generic_hdr->src == 0) ? "_" : "",
			(rreq_packet->getType() == CBRTYPE_RREQ1) ? '1' : '2',
			generic_hdr->src, localID,
			rreq_packet->getControl(), rreq_packet->getData());

	reqtable.insert(trID, rreq_packet->getSource(), prev_hop);

	const cbr_ContextAddress* addr = rreq_packet->getAddress();

	// FIXME: this looks very wrong to forward the packet before
	//	  checking if it is destined for the local node
	forward_rreq(addr, rreq_packet, delay);

	// FIXME: if the packet is unicast RREQ and it has been forwarded
	// in previous step, and now we will send a RREP for it, it is
	// possible, that RREQ sender will receive more than one RREP
	// for it's unicast RREQ -> RESOLVED

	// check if this packet is for me
	if (local_context_model == 0) {
		bool b;
		if (cbr_checkCBA(addr->getAsString().c_str(), b) == -1 || !b) {
			delete rreq_packet;
			return;
		}
	} else {
		const cbr_ConceptsSet* me = localAddr->getAsSet();
		cbr_ConceptsSet::const_iterator i, j;
		for (i = me->begin(); i != me->end(); i++) {
			if (addr->matches(*i))
				break;
			cbr_ConceptsSet super;
			model.getSuperClasses(*i, super);
			for (j = super.begin(); j != super.end(); j++) {
				if (addr->matches(*j))
					break;
			}
			if (j != super.end())
				break;
		}

		if (i == me->end()) {
			delete rreq_packet;
			return;
		}
	}

	if (rreq_packet->getType() == CBRTYPE_RREQ1) {
		cbr_Msg* msg = new cbr_Msg(
				rreq_packet->getTotalLength(),
				rreq_packet->getPayloadLength(),
				rreq_packet->getPayload(), addr,
				rreq_packet->getTransactionID(),
				cbr_Msg::INCOMPLETE,
				INCOMING_MSG_LIFE);
		incoming.enqueue(msg);
		sendReply(rreq_packet, prev_hop);
	} else {
		cbr_debug("RECEIVED ENTIRE MESSAGE!\n");
		if (local_context_model == false)
			cbr_PutMsg((void *)rreq_packet->getPayload(), 
			CBR_ID_LOCAL, rreq_packet->getPayloadLength());
	}
	cbr_debug("DISCOVERED DESTINATION\n");

	delete rreq_packet;
}

/**
 * Sends RREP packet.
 *
 * @param rr cbr_RReqPacket packet for which send reply
 * @param fwdr ID of the forwaring node
 *
 * Called by CBR::recvRequest.
 */

void CBR::sendReply(cbr_RReqPacket* rr, cbr_ID fwdr) {
	cbr_RRepPacket *rrep_packet = new cbr_RRepPacket(localID, rr->getSource(), rr->getTransactionID());

	cbr_debug("%sSEND : RREP from %d to %d | %d | %d\n",
			rr->getSource() == 0 ? "_" : "",
			(int)localID, (int)rr->getSource(),
			rrep_packet->getControl(), rrep_packet->getData());

	sendPacket(rrep_packet, fwdr);
	delete rrep_packet;
}

/**
 * Handle received packet of type RREP.
 *
 * @param pkt generic packet
 *
 * Processes the RREP packet.
 *
 * If it is destined for the local node then:
 * - remove the transaction ID of the packet (?) from reqtable
 * - insert the transaction ID, the source node (== the destination node from
 *   the point of view of the local node) and the previous hop into rtable
 * - lookup the outgoing message queue for the transaction ID (+error checking)
 * - send DATA packets until all data are sent and set message state to sent
 *
 * If it is not destined for the local node then:
 * - get the previous hop for the transaction ID of the packet from reqtable
 * - insert the transaction ID, the source node and the previous hop of the
 *   packet into rtable
 * - forward the packet to the previous hop taken from the reqtable
 *
 * Called by CBR::recv.
 */

void CBR::recvReply(Packet* pkt) {
	cbr_RRepPacket* rrep_packet = new cbr_RRepPacket(pkt);

	cbr_debug("%sRECV : RREP from %u to %u @ %u | %d | %d\n",
			(localID == 0 && rrep_packet->getDestination() == 0) ? "_" : "",
			rrep_packet->getSource(), rrep_packet->getDestination(),
			localID, rrep_packet->getControl(), rrep_packet->getData());

	struct hdr_cmn *ch= HDR_CMN(pkt);
	cbr_ID prev_hop = ch->prev_hop_, src = rrep_packet->getSource();

	if (rrep_packet->getDestination() == localID) {
		reqtable.remove(rrep_packet->getTransactionID(), src);
		rtable.insert(rrep_packet->getTransactionID(), src, prev_hop);

		cbr_Msg* msg = outgoing.lookup(rrep_packet->getTransactionID());
		if (msg == NULL) {
cbr_log("DROPPING: unknown transaction\n");
						Packet::free(pkt);
			delete rrep_packet;
			return;
		}
		if (!msg->isMulticast() && msg->getState() != cbr_Msg::RREQ) {
cbr_log("DROPPING: already sent this ucast message\n");
						Packet::free(pkt);
			delete rrep_packet;
			return;
		}

		char tmp[cbr_DataPacket::maxPayload];
		int32_t offset, len;
		offset = msg->getCurrentLength();
		while (offset < msg->getTotalLength()) {
			len = msg->getPayload(tmp, offset, cbr_DataPacket::maxPayload);
			cbr_DataPacket *data_packet = new cbr_DataPacket(
					localID, rrep_packet->getSource(),
					rrep_packet->getTransactionID(),
					tmp, offset, len);

			cbr_debug("%sSEND : DATA from %d to %d by %d | %d | %d\n",
					localID == 0 ? "" : "_", localID,
					rrep_packet->getSource(), prev_hop,
					data_packet->getControl(),
					data_packet->getData());

			sendPacket(data_packet, prev_hop);
			delete data_packet;
			offset += len;
		}
		msg->setState(cbr_Msg::SENT);

		Packet::free(pkt);
	} else {
		u_int32_t trID = rrep_packet->getTransactionID();
		cbr_ID src = rrep_packet->getSource(), f;

		if (reqtable.getPrevHop(trID, src, &f)) {
			rtable.insert(trID, src, prev_hop);
			forward(f, pkt, 0.0);
		} else {
			cbr_debug("DROPPING: unknown route\n");
			Packet::free(pkt);
		}
	}

	delete rrep_packet;
}

/**
 * Sends HELLO packet.
 *
 * Send HELLO packet containing both internal and external concepts.
 * If there is not enought room for all concepts then concept compession
 * is used (default or fair compression depending on HELLO_COMPRESSION
 * define).
 */

void CBR::sendHello() {
	cbr_TriplesList::const_iterator i;
	cbr_TriplesList external = crtable.getAsTriples();
	cbr_TriplesList internal = localAddr->getAsTriples(localID);
	cbr_TriplesList list;
	int curr_length = 0;
	int max_length = cbr_Packet::maxSize - sizeof(hdr_cbr) - 2;

	//	cbr_TriplesList_dump("int", &internal); //
	//	cbr_TriplesList_dump("ext", &external); //

	for (i = internal.begin(); i != internal.end(); i++)
		curr_length += (*i)->getByteLength();
	for (i = external.begin(); i != external.end(); i++)
		curr_length += (*i)->getByteLength();

	if (curr_length <= max_length) {
		list.assign(internal.begin(), internal.end());
		list.insert(list.end(), external.begin(), external.end());
		cbr_debug("no compression\n");
	} else {
		cbr_debug("compression used\n");
		// compression is needed
#if HELLO_COMPRESSION == 0
		// default compression method
		int len = 0;
		for (i = external.begin(); i != external.end(); i++) {
			list.push_back(*i);
			len += (*i)->getByteLength();
			if (len > max_length)
				len = compr.compress(list, model, max_length);
		}
		for (i = internal.begin(); i != internal.end(); i++) {
			list.push_back(*i);
			len += (*i)->getByteLength();
			if (len > max_length)
				len = compr.compress(list, model, max_length);
		}
#else
		// fair compression method
		list.assign(internal.begin(), internal.end());
		list.insert(list.end(), external.begin(), external.end());
		compr.compress(list, model, max_length);
#endif
	}

	//	cbr_TriplesList_dump("list", &list); //

	cbr_HelloPacket *hello_packet = new cbr_HelloPacket(localID, list);
	cbr_debug("SEND : HELLO from %d | %d | %d\n", (int)localID,
			hello_packet->getControl(), hello_packet->getData());
	sendPacket(hello_packet, IP_BROADCAST);
	delete hello_packet;
}

/**
 * Handles received HELLO packet.
 *
 * @param pkt generic packet
 *
 * Updates context routing table using data from HELLO packet
 *
 * Called by CBR::recv.
 */

void CBR::recvHello(Packet *pkt) {
	hdr_cbr* generic_hdr = hdr_cbr::access(pkt);

	cbr_HelloPacket* hello_packet = new cbr_HelloPacket(pkt);
	cbr_debug("RECV : HELLO from %u on %u | %d | %d\n", generic_hdr->src,
			localID, hello_packet->getControl(), hello_packet->getData());
	cbr_TriplesList internal, external;
	cbr_TriplesList::const_iterator i;

	struct hdr_cmn *ch= HDR_CMN(pkt);
	cbr_ID prev_hop = ch->prev_hop_;

	// update context routing table using data from HELLO packet,
	// do not update "external" concepts for the local node
	hello_packet->getTriples(internal, external);
	for (i = internal.begin(); i != internal.end(); i++)
		crtable.update(*(*i), prev_hop);
	for (i = external.begin(); i != external.end(); i++) {
		if ((*i)->getSource() != localID) {
			crtable.update(*(*i), prev_hop);
		}
	}

	Packet::free(pkt);
	delete hello_packet;
}

/**
 * Sends DATA packet.
 *
 * @param msg CBR message
 * @param delay delay (in seconds)
 *
 * Queue @msg on the outgoing message queue.  Then construct RREQ1 or RREQ2
 * packet (type depends on the message size) and send it using the packet
 * forwarding handling.
 *
 * Used for all messages generated by the local node.
 */

void CBR::sendDataMessage(cbr_Msg* msg, double delay) {
	outgoing.enqueue(msg);

	bool mcast = msg->isMulticast();
	cbr_RReqPacket* rreq_packet = new cbr_RReqPacket(localID, msg, mcast);

	cbr_debug("%sSEND : RREQ%c from %d | %d | %d\n",
			localID ? "":"_",
			rreq_packet->getType() == CBRTYPE_RREQ1 ? '1' : '2',
			localID, rreq_packet->getControl(), rreq_packet->getData());

	recvRequest(NULL, rreq_packet, delay);
}

/**
 * Handles received DATA packet.
 *
 * @param pkt generic packet
 *
 * Process the DATA packet.
 *
 * If it is destined for the local node then:
 * - lookup the incoming messages queue for transaction ID of the packet
 * - if found then update the message payload with the data from the packet,
 *   if the payload is fully received set the message state to complete
 *   and remove the message from the incoming messages queue
 * - free packet
 *
 * If it is not destined for the local node then:
 * - get the forwarded ID from the rtable given the transaction ID
 *   and the destination node ID
 * - forward the packet to the next hop taken from the rtable
 *
 * Called by CBR::recv.
 */

void CBR::recvData(Packet *pkt) {
	cbr_DataPacket* data_packet = new cbr_DataPacket(pkt);
	hdr_cbr* generic_hdr = hdr_cbr::access(pkt);

	cbr_debug("%sRECV : DATA from %u to %u @ %u | %d | %d\n",
			(data_packet->getDestination() == localID) ? "_" : "",
			generic_hdr->src, data_packet->getDestination(), localID,
			data_packet->getControl(), data_packet->getData());

	if (data_packet->getDestination() == localID) {
		cbr_Msg* msg = incoming.lookup(data_packet->getTransactionID());
		if (msg != NULL) {
			msg->add(data_packet->getPayload(), data_packet->getOffset(),
					data_packet->getPayloadLength());
			if (msg->getState() == cbr_Msg::COMPLETE) {
				cbr_debug("RECEIVED ENTIRE MESSAGE!\n");
				char *tmp = new char[msg->getTotalLength()];
				msg->getPayload(tmp, 0, msg->getTotalLength());
				if (local_context_model == false)
					cbr_PutMsg(tmp, CBR_ID_LOCAL, msg->getTotalLength());
				// remove message
				incoming.lookup(msg->getTransactionID(), true);
				delete msg;
			}
		}

		Packet::free(pkt);
	} else {
		cbr_ID f;

		if (rtable.getForwarderID(data_packet->getTransactionID(),
				data_packet->getDestination(), &f)) {
			forward(f, pkt, 0.0);
		} else {
cbr_log("DROPPING: unknown route\n");
						Packet::free(pkt);
		}
	}

	delete data_packet;
}
